package com.wzy.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wzy.config.properties.OssProperties;
import com.wzy.domain.OssFile;
import com.wzy.domain.ResponseResult;
import com.wzy.domain.bo.ArticleBO;
import com.wzy.domain.bo.ArticleExamineBO;
import com.wzy.domain.entity.Article;
import com.wzy.domain.entity.ArticleRecord;
import com.wzy.domain.entity.Category;
import com.wzy.domain.entity.EditHistory;
import com.wzy.domain.vo.*;
import com.wzy.exception.SystemException;
import com.wzy.mapper.ArticleMapper;
import com.wzy.mapper.ArticleRecordMapper;
import com.wzy.service.ArticleService;
import com.wzy.service.CategoryService;
import com.wzy.service.UserCommonService;
import com.wzy.util.BeanCopyUtils;
import com.wzy.util.MinioTemplate;
import com.wzy.util.RedisCache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

import static com.wzy.constant.RedisConstants.*;
import static com.wzy.constant.SystemConstants.SUCCESS;
import static com.wzy.constant.SystemConstants.*;
import static com.wzy.enums.AppHttpCodeEnum.*;

/**
 * 文章业务层
 */
@Service("ArticleServiceImpl")
@RequiredArgsConstructor
@Slf4j
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article>
        implements ArticleService
{
    private final CategoryService categoryService;
    private final RedisCache redisCache;
    private final ArticleMapper articleMapper;
    private final ArticleRecordMapper articleRecordMapper;
    private final HttpServletRequest httpServletRequest;
    private final MinioTemplate minioTemplate;
    private final OssProperties ossProperties;
    private final UserCommonService userCommonService;

    /**
     * 查询热门文章列表
     */
    public ResponseResult<Collection<HotArticleVO>> getHotArticleList()
    {
        if (Boolean.FALSE.equals(redisCache.hasKey(ARTICLE_Z_SET_KEY)))
        {
            // 查询浏览量最高的前10篇文章的信息
            LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Article::getArticleStatus, RELEASE)
                    .eq(Article::getArticleExamine, PASS)
                    .orderByDesc(Article::getViewCount);
            // 最多查询10条记录
            Page<Article> page = new Page<>(1, 10);
            page(page, queryWrapper);

            List<Article> articles = page.getRecords();
            // 查询文章所属分类
            articles.forEach(article -> article.setCategoryName(((Function<Article, String>) article1 -> {
                Long categoryId = article1.getCategoryId();
                return categoryService.getById(categoryId).getName();
            }).apply(article)));

            List<HotArticleVO> hotArticleVOS = BeanCopyUtils.copyBeanList(articles, HotArticleVO.class);
            // 从 redis中获取浏览量
            hotArticleVOS.forEach(hotArticleVO ->
            {
                String redisKey = REDIS_ARTICLE_VIEW_KEY + hotArticleVO.getId().toString();
                hotArticleVO.setViewCount(Long.valueOf(redisCache.getCacheObject(redisKey).toString()));
            });
            return ResponseResult.okResult(hotArticleVOS);
        }
        else
        {
            return ResponseResult.okResult(redisCache.getReverseRange(ARTICLE_Z_SET_KEY, 0, 10));
        }
    }

    /**
     * 查询最新发布文章
     */
    @Override
    public ResponseResult<List<ArticleListVO>> getLatestArticleList()
    {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Article::getArticleStatus, RELEASE)
                .eq(Article::getArticleExamine, PASS)
                .orderByDesc(Article::getCreateTime)
                .last("limit 0,3");
        List<Article> articles = articleMapper.selectList(queryWrapper);

        List<ArticleListVO> articleListVOS = BeanCopyUtils.copyBeanList(articles, ArticleListVO.class);
        return ResponseResult.okResult(articleListVOS);
    }

    /**
     * 分页查询文章列表
     */
    @Override
    public ResponseResult<PageVO> getArticleListByCategoryId(Integer pageNum, Integer pageSize, Long categoryId)
    {
        PageVO pageVO;
        if ((Objects.nonNull(categoryId) && categoryId > 0L) || Boolean.FALSE.equals(redisCache.hasKey(ARTICLE_Z_SET_KEY)))
        {
            LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
            // 查询正式发布的文章
            queryWrapper.eq(Article::getArticleStatus, RELEASE)
                    .eq(Article::getArticleExamine, PASS);
            // 如果有对应的 categoryId,则查询对应分类的文章
            queryWrapper.eq(Objects.nonNull(categoryId) && categoryId > 0L,
                    Article::getCategoryId, categoryId);
            // 将置顶文章置顶，按浏览量排序
            queryWrapper.orderByDesc(Article::getIsTop)
                    .orderByDesc(Article::getViewCount);

            // 分页查询
            Page<Article> page = new Page<>(pageNum, pageSize);
            page(page, queryWrapper);

            List<Article> articles = page.getRecords();
            // 查询 categoryName
            articles.forEach(article -> article.setCategoryName(((Function<Article, String>) article1 -> {
                Long id = article1.getCategoryId();
                return categoryService.getById(id).getName();
            }).apply(article)));

            List<ArticleListVO> articleListVOS = BeanCopyUtils.copyBeanList(articles, ArticleListVO.class);
            // 从 redis中获取浏览量
            articleListVOS.forEach(articleListVO ->
            {
                String redisKey = REDIS_ARTICLE_VIEW_KEY + articleListVO.getId().toString();
                articleListVO.setViewCount(Long.valueOf(redisCache.getCacheObject(redisKey).toString()));
            });
            pageVO = new PageVO(articleListVOS, page.getTotal());
        }
        else
        {
            Set<ArticleListVO> zSetRange = redisCache.getReverseRange(ARTICLE_Z_SET_KEY,
                    pageSize * (pageNum - 1), pageSize * pageNum - 1);
            pageVO = new PageVO(zSetRange, redisCache.getZSetSize(ARTICLE_Z_SET_KEY));
        }
        return ResponseResult.okResult(pageVO);
    }

    /**
     * 获取文章详情
     */
    @Override
    public ResponseResult<ArticleVO> getArticle(Long id)
    {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Article::getArticleStatus, RELEASE)
                .eq(Article::getArticleExamine, PASS)
                .eq(Article::getId, id);
        Article article = getOne(queryWrapper);
        if (Objects.isNull(article))
        {
            throw new SystemException(SORRY_NOT_FOUND, "没有找到文章");
        }
        // 获取文章所属分类名
        Category category = categoryService.getById(article.getCategoryId());
        article.setCategoryName(category.getName());

        ArticleVO articleVO = BeanCopyUtils.copyBean(article, ArticleVO.class);
        // 从 redis中获取浏览量
        String redisKey = REDIS_ARTICLE_VIEW_KEY + articleVO.getId().toString();
        articleVO.setViewCount(Long.valueOf(redisCache.getCacheObject(redisKey).toString()));

        return ResponseResult.okResult(articleVO);
    }

    /**
     * 更新文章浏览量
     */
    @Override
    public ResponseResult<Void> updateViewCount(Long id)
    {
        String redisKey = REDIS_ARTICLE_VIEW_KEY + id;
        if (Boolean.FALSE.equals(redisCache.hasKey(redisKey)))
        {
            throw new SystemException(SORRY_NOT_FOUND);
        }
        redisCache.increment(redisKey);
        return ResponseResult.okResult();
    }

    /**
     * 上传文章图片
     */
    @Override
    public ResponseResult<OssFile> uploadImage(MultipartFile file)
    {
        Long userId = userCommonService.getRedisUserId(httpServletRequest);
        if (file.getSize() > IMAGE_SIZE)
            throw new SystemException(SYSTEM_ERROR, "图片不能超过1MB");
        if (!StringUtils.equalsAny(file.getContentType(), JPG, JPEG, PNG))
            throw new SystemException(SYSTEM_ERROR, "图片只能是 JPG/JPEG/PNG 格式");
        OssFile ossFile;
        try
        {
            // 上传图片
            ossFile = minioTemplate.putObjectByDefine(file.getInputStream(), null,
                    file.getOriginalFilename(), file.getContentType(), userId.toString());
        }
        catch (IOException e)
        {
            throw new SystemException(SYSTEM_ERROR);
        }
        return ResponseResult.okResult(ossFile);
    }

    /**
     * 删除文章图片
     */
    @Override
    public ResponseResult<OssFile> deleteImage(String url)
    {
        String userId = userCommonService.getRedisUserId(httpServletRequest).toString();
        String[] split = url.split(StrUtil.SLASH);
        log.debug("split:{}", split[4]);
        if (!userId.equals(split[4]))
        {
            throw new SystemException(NO_OPERATOR_AUTH);
        }
        String defaultBucketName = ossProperties.getDefaultBucketName();
        int i = url.indexOf(defaultBucketName) + defaultBucketName.length() + 1;
        log.info("删除了文件-fileName:{}", url.substring(i));
        minioTemplate.removeObject(null, url.substring(i));
        return ResponseResult.okResult();
    }

    /**
     * 保存文章
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult<Object> postArticle(SaveArticleVO article)
    {
        String userRole = userCommonService.getRedisUserRole(httpServletRequest);
        Article saveArticle = BeanCopyUtils.copyBean(article, Article.class);
        boolean isAdmin = userRole.equals(ADMIN_ROLE);
        if (isAdmin)
        {
            saveArticle.setArticleExamine(PASS);
        }
        else
        {
            saveArticle.setArticleExamine(NOT_EXAMINE);
        }
        // 获取当前时间
        String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        String editKey = REDIS_ARTICLE_EDIT_LIST_KEY + saveArticle.getCreateBy();
        if (article.getId() == null)
        {
            // 保存文章
            articleMapper.insert(saveArticle);
            if (saveArticle.getArticleStatus().equals(RELEASE) && isAdmin)     //发布文章
            {
                // 将文章浏览量同步到 redis
                String articleKey = REDIS_ARTICLE_VIEW_KEY + saveArticle.getId();
                // viewCount为null时为新发布的文章或草稿，不为空时为已发布文章
                long viewCount = article.getViewCount() == null ? 1 : article.getViewCount();
                redisCache.setCacheObject(articleKey, BigInteger.valueOf(viewCount));
                // 将事件 push入消息队列
                EditHistory editHistory = new EditHistory(null, "发布了文章：" + saveArticle.getTitle(),
                        now, GREEN, null, NORMAL);
                redisCache.setCacheList(editKey, Collections.singletonList(editHistory));
                return ResponseResult.okResult(saveArticle.getId(), "发布成功");
            }
            else if (saveArticle.getArticleStatus().equals(RELEASE) && !isAdmin)
            {
                // 将文章浏览量同步到 redis
                String articleKey = REDIS_ARTICLE_VIEW_KEY + saveArticle.getId();
                // viewCount为null时为新发布的文章或草稿，不为空时为已发布文章
                long viewCount = article.getViewCount() == null ? 1 : article.getViewCount();
                redisCache.setCacheObject(articleKey, BigInteger.valueOf(viewCount));
                // 将事件 push入消息队列
                EditHistory editHistory = new EditHistory(null, "提交了文章审核申请：" +
                        saveArticle.getTitle(), now, GREEN, UPLOAD, LARGE);
                redisCache.setCacheList(editKey, Collections.singletonList(editHistory));
                return ResponseResult.okResult(saveArticle.getId(), "审核申请成功");
            }
            else    //草稿
            {
                // 将事件 push入消息队列
                EditHistory editHistory = new EditHistory(null, "创建了草稿：" +
                        saveArticle.getTitle(), now, GREEN, null, NORMAL);
                redisCache.setCacheList(editKey, Collections.singletonList(editHistory));
                return ResponseResult.okResult(200, "草稿已保存");
            }
        }
        else
        {
            // 更新文章
            Article previousArticle = articleMapper.selectById(article.getId());
            ArticleRecord articleRecord = BeanCopyUtils.copyBean(previousArticle, ArticleRecord.class);
            articleRecord.setCreateTime(previousArticle.getUpdateTime());   // 记录创建时间
            articleRecordMapper.insert(articleRecord);
            articleMapper.updateById(saveArticle);
            if (saveArticle.getArticleStatus().equals(RELEASE) && isAdmin)  //编辑草稿并发布文章
            {
                // 将文章浏览量同步到 redis
                String articleKey = REDIS_ARTICLE_VIEW_KEY + saveArticle.getId();
                // viewCount为null时为新发布的文章或草稿，不为空时为已发布文章
                long viewCount = article.getViewCount() == null ? 1 : article.getViewCount();
                redisCache.setCacheObject(articleKey, BigInteger.valueOf(viewCount));
                // 将事件 push入消息队列
                EditHistory editHistory = new EditHistory(articleRecord.getRecordId(), "编辑了文章：" +
                        saveArticle.getTitle(), now, BLUE, null, NORMAL);
                redisCache.setCacheList(editKey, Collections.singletonList(editHistory));
                return ResponseResult.okResult(saveArticle.getId(), "编辑成功");
            }
            else if (saveArticle.getArticleStatus().equals(RELEASE) && !isAdmin)
            {
                // 将文章浏览量同步到 redis
                String articleKey = REDIS_ARTICLE_VIEW_KEY + saveArticle.getId();
                // viewCount为null时为新发布的文章或草稿，不为空时为已发布文章
                long viewCount = article.getViewCount() == null ? 1 : article.getViewCount();
                redisCache.setCacheObject(articleKey, BigInteger.valueOf(viewCount));
                // 将事件 push入消息队列
                EditHistory editHistory = new EditHistory(articleRecord.getRecordId(),
                        "提交了编辑文章审核申请：" + saveArticle.getTitle(), now, BLUE, UPLOAD, LARGE);
                redisCache.setCacheList(editKey, Collections.singletonList(editHistory));
                return ResponseResult.okResult(saveArticle.getId(), "审核申请成功");
            }
            else    //编辑草稿
            {
                // 将事件 push入消息队列
                EditHistory editHistory = new EditHistory(articleRecord.getRecordId(),
                        "编辑了草稿：" + saveArticle.getTitle(), now, YELLOW, null, NORMAL);
                redisCache.setCacheList(editKey, Collections.singletonList(editHistory));
                return ResponseResult.okResult(200, "草稿已保存");
            }
        }
    }

    /**
     * 获取草稿
     */
    @Override
    public ResponseResult<List<SaveArticleVO>> getDraftList()
    {
        Long userId = userCommonService.getRedisUserId(httpServletRequest);
        // 获取草稿
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Article::getArticleStatus, NOT_RELEASE);
        queryWrapper.eq(Article::getCreateBy, userId);
        List<Article> articles = articleMapper.selectList(queryWrapper);
        List<SaveArticleVO> saveArticleVOS = BeanCopyUtils.copyBeanList(articles, SaveArticleVO.class);

        return ResponseResult.okResult(saveArticleVOS);
    }

    /**
     * 获取编辑记录
     */
    @Override
    public ResponseResult<List<Object>> getEditHistory()
    {
        Long userId = userCommonService.getRedisUserId(httpServletRequest);
        // 获取编辑记录
        String editKey = REDIS_ARTICLE_EDIT_LIST_KEY + userId;
        List<Object> cacheList = redisCache.getCacheList(editKey);
        return ResponseResult.okResult(cacheList);
    }

    /**
     * 删除草稿
     */
    @Override
    public ResponseResult<Void> deleteDraft(Long articleId)
    {
        Long userId = userCommonService.getRedisUserId(httpServletRequest);
        // 获取标题
        String title = getById(articleId).getTitle();
        // 删除草稿
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Article::getCreateBy, userId);
        queryWrapper.eq(Article::getId, articleId);
        articleMapper.delete(queryWrapper);
        // 获取时间戳
        String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        String editKey = REDIS_ARTICLE_EDIT_LIST_KEY + userId;
        // 将事件 push入消息队列
        EditHistory editHistory = new EditHistory(null, "删除了草稿：" + title, now,
                RED, null, NORMAL);
        redisCache.setCacheList(editKey, Collections.singletonList(editHistory));

        return ResponseResult.okResult();
    }

    /**
     * 获取我发布的文章总数
     */
    @Override
    public ResponseResult<Map<String, Integer>> getMyArticleTotal()
    {
        Long userId = userCommonService.getRedisUserId(httpServletRequest);
        //根据 userId查询已发布文章
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Article::getCreateBy, userId)
                .eq(Article::getArticleStatus, RELEASE);
        Integer integer = articleMapper.selectCount(queryWrapper);
        Map<String, Integer> map = new HashMap<>();
        map.put("myArticleTotal", integer);
        return ResponseResult.okResult(map);
    }

    /**
     * 获取我发布的文章总浏览量
     */
    @Override
    public ResponseResult<Map<String, Long>> getTotalView()
    {
        Long userId = userCommonService.getRedisUserId(httpServletRequest);
        // 根据 userId获取发布的文章总浏览量
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("create_by", userId);
        queryWrapper.select("IFNULL(SUM(view_count),0) AS view_count");
        Article article = articleMapper.selectOne(queryWrapper);
        Map<String, Long> map = new HashMap<>();
        map.put("totalView", article.getViewCount());

        return ResponseResult.okResult(map);
    }

    /**
     * 查询提交审核文章
     */
    @Override
    public ResponseResult<PageVO> getNotExamineArticle(ArticleExamineBO articleExamineBO)
    {
        userCommonService.checkRole(httpServletRequest);
        LambdaQueryWrapper<Article> qw = new LambdaQueryWrapper<Article>()
                .eq(Article::getArticleStatus, RELEASE)
                .in(StringUtils.isEmpty(articleExamineBO.getArticleExamine()),
                        Article::getArticleExamine, NOT_EXAMINE, NOT_PASS)
                .eq(!StringUtils.isEmpty(articleExamineBO.getArticleExamine()),
                        Article::getArticleExamine, articleExamineBO.getArticleExamine())
                .eq(Objects.nonNull(articleExamineBO.getCreateBy()),
                        Article::getCreateBy, articleExamineBO.getCreateBy())
                .ge(!StringUtils.isEmpty(articleExamineBO.getStartTime()),
                        Article::getCreateTime, articleExamineBO.getStartTime())
                .le(!StringUtils.isEmpty(articleExamineBO.getEndTime()),
                        Article::getCreateTime, articleExamineBO.getEndTime());
        Page<Article> page = articleExamineBO.build();
        page(page, qw);
        return ResponseResult.okResult(new PageVO(page.getRecords(), page.getTotal()));
    }

    /**
     * 修改审核文章状态
     */
    @Override
    public ResponseResult<Void> editArticleExamine(Article article)
    {
        userCommonService.checkRole(httpServletRequest);
        if (article.getArticleStatus().equals(NOT_RELEASE))
        {
            throw new SystemException(PARAMETER_ERROR, "非法操作！");
        }
        int rows = articleMapper.updateById(article);
        // 获取时间戳
        String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        String editKey = REDIS_ARTICLE_EDIT_LIST_KEY + article.getCreateBy();
        if (rows > 0)
        {
            // 将文章浏览量同步到 redis
            String articleKey = REDIS_ARTICLE_VIEW_KEY + article.getId();
            // viewCount为null时为新发布的文章或草稿，不为空时为已发布文章
            long viewCount = article.getViewCount() == null ? 1 : article.getViewCount();
            redisCache.setCacheObject(articleKey, BigInteger.valueOf(viewCount));
            EditHistory editHistory;
            if (article.getArticleExamine().equals(PASS))
            {
                // 将事件 push入消息队列
                editHistory = new EditHistory(null, "文章审核通过：" + article.getTitle(),
                        now, GREEN, SUCCESS, LARGE);
            }
            else if (article.getArticleExamine().equals(NOT_PASS))
            {
                // 将事件 push入消息队列
                editHistory = new EditHistory(null, "文章审核未通过：" + article.getTitle() +
                        "(" + article.getNotPassMessage() + ")", now, RED, FAIL, LARGE);
            }
            else
            {
                throw new SystemException(PARAMETER_ERROR, "非法操作！");
            }
            redisCache.setCacheList(editKey, Collections.singletonList(editHistory));
        }
        return ResponseResult.resultByRows(rows);
    }

    /**
     * 获取文章编辑详情
     */
    @Override
    public ResponseResult<ArticleRecord> getArticleEditRecord(Long recordId)
    {
        Long userId = userCommonService.getRedisUserId(httpServletRequest);
        ArticleRecord articleRecord = articleRecordMapper.selectOne(new LambdaQueryWrapper<ArticleRecord>()
                .eq(ArticleRecord::getRecordId, recordId)
                .eq(ArticleRecord::getCreateBy, userId));
        if (articleRecord == null)
        {
            throw new SystemException(PARAMETER_ERROR, "未找到草稿，请联系管理员");
        }
        return ResponseResult.okResult(articleRecord);
    }

    /**
     * 管理员查询文章
     */
    @Override
    public ResponseResult<PageVO> selectPageArticle(ArticleBO articleBO)
    {
        userCommonService.checkRole(httpServletRequest);
        Page<Article> page = articleBO.build();
        page(page, new LambdaQueryWrapper<Article>()
                .orderByDesc(Article::getCreateTime)
                .like(!StringUtils.isEmpty(articleBO.getTitle()), Article::getTitle, articleBO.getTitle())
                .eq(!StringUtils.isEmpty(articleBO.getIsTop()), Article::getIsTop, articleBO.getIsTop())
                .eq(!StringUtils.isEmpty(articleBO.getArticleStatus()), Article::getArticleStatus, articleBO.getArticleStatus())
                .eq(!StringUtils.isEmpty(articleBO.getArticleExamine()), Article::getArticleExamine, articleBO.getArticleExamine())
                .eq(!StringUtils.isEmpty(articleBO.getIsComment()), Article::getIsComment, articleBO.getIsComment())
                .eq(Objects.nonNull(articleBO.getCategoryId()), Article::getCategoryId, articleBO.getCategoryId())
                .eq(Objects.nonNull(articleBO.getCreateBy()), Article::getCreateBy, articleBO.getCreateBy())
                .ge(!StringUtils.isEmpty(articleBO.getStartTime()), Article::getCreateTime, articleBO.getStartTime())
                .le(!StringUtils.isEmpty(articleBO.getEndTime()), Article::getCreateTime, articleBO.getEndTime()));
        List<Article> articles = page.getRecords();
        PageVO pageVO = new PageVO(articles, page.getTotal());

        return ResponseResult.okResult(pageVO);
    }

}




